package com.example.origin.finance.trade.dto;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.example.origin.finance.dto.XueqiuKline;
import com.example.origin.finance.trade.enums.Action;
import com.example.origin.finance.utils.CalcUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Data
public class Trader {

    private List<InfoResource> infoResources = new ArrayList<>();

    private MultiValueMap<String, TradeRecord> tradeRecordMap = new LinkedMultiValueMap<>();
    private Map<String, HoldRecord> holdRecordMap = new LinkedHashMap<>();
    private Double initCashAsset = 100000000d;
    private Double perMaxPrice = 1000d;
    private Double cashAsset = initCashAsset;
    private Double asset = cashAsset;
    private Double stockAsset = 0d;
    private Analyst analyst;

    private void statAsset() {
        setAsset(getCashAsset() + getStockAsset());
    }


    public void buyOrSell() {
        Conclusion conclusion = conclusion();
        Action action = conclusion.getAction();
        InfoResource infoResource = conclusion.getInfoResource();
        String code = infoResource.getCode();
        Date time = infoResource.getTime();
        XueqiuKline xueqiuKline = infoResource.getKline();
        if (action.equals(Action.BUY)) {
            if (getTradeRecordMap().containsKey(code)) {
                if (getTradeRecordMap().get(code).stream().anyMatch(TradeRecord::isBought)) {
                    return;
                }
            }
            TradeRecord tradeRecord = new TradeRecord();
            int volume = CalcUtil.longDivide(perMaxPrice, xueqiuKline.getClose()).intValue() * 100;
            if (volume < 100) {
                return;
            }
            tradeRecord.recordBuy(code, time, xueqiuKline.getClose(), (long) volume);
            Double buyTotalPrice = tradeRecord.getBuyTotalPrice();
            if (buyTotalPrice > getCashAsset()) {
                return;
            }

            getTradeRecordMap().add(code, tradeRecord);
            statCashAsset(buyTotalPrice, false);

        } else if (action.equals(Action.SELL)) {
            if (!getTradeRecordMap().containsKey(code)) {
                return;
            }
            List<TradeRecord> tradeRecords = getTradeRecordMap().get(code);
            for (int i = tradeRecords.size() - 1; i >= 0; i--) {
                TradeRecord tradeRecord = tradeRecords.get(i);
                if (tradeRecord.isBought()) {
                    tradeRecord.recordSell(time, xueqiuKline.getClose(), tradeRecord.getBuyVolume());
                    Double sellTotalPrice = tradeRecord.getSellTotalPrice();
                    statCashAsset(sellTotalPrice, true);
                    return;
                }
            }
        }

    }

    public void logAsset(Date date, Map<String, XueqiuKline> klineMap) {

        List<Double> doubles = new ArrayList<>();
        List<String> newSolds = new ArrayList<>();
        List<String> newBoughts = new ArrayList<>();
        for (List<TradeRecord> tradeRecords : getTradeRecordMap().values()) {
            TradeRecord record = tradeRecords.get(tradeRecords.size()-1);
            if (record.isSold()&&DateUtil.beginOfWeek(date).equals(DateUtil.beginOfWeek(record.getSellTime()))) {
                newSolds.add(record.getCode());
            }
            for (TradeRecord tradeRecord : tradeRecords) {
                if (tradeRecord.isBought()) {
                    String code = tradeRecord.getCode();
                    TradeRecord copyBean = BeanUtil.copyProperties(tradeRecord, TradeRecord.class);
                    if (klineMap.containsKey(code)) {
                        XueqiuKline xueqiuKline = klineMap.get(code);
                        Double close = xueqiuKline.getClose();
                        copyBean.recordSell(date, close, tradeRecord.getBuyVolume());
                        if (copyBean.getProfit()==0) {
                            newBoughts.add(code);
                        }
                        addHoldRecord(date,copyBean,xueqiuKline);
                        doubles.add(copyBean.getSellTotalPrice());
                    } else {
                        addHoldRecord(date,copyBean,null);
                        doubles.add(tradeRecord.getBuyTotalPrice());
                    }
                }
            }
        }
        double sum = doubles.stream().mapToDouble(e -> e).sum();
        setStockAsset(sum);
        statAsset();

        Map<String, String> buyCodeMap = getBuyCodeMap(date);
        log.info("{},asset:{},cashAsset:{},stockAsset:{},count:{},newBoughts:{},newSolds:{},codes:{}", DateUtil.formatDate(date), CalcUtil.strOf(getAsset()),
                CalcUtil.strOf(getCashAsset()), CalcUtil.strOf(getStockAsset()), buyCodeMap.size(),JSONUtil.toJsonStr(newBoughts),JSONUtil.toJsonStr(newSolds), JSONUtil.toJsonStr(buyCodeMap));
    }

    private Map<String, String> getBuyCodeMap(Date date) {
        Map<String, String> buyCodeMap = new LinkedHashMap<>();
        for (Pair<TradeRecord, XueqiuKline> dateRecord : sortList(date)) {
            TradeRecord tradeRecord = dateRecord.getKey();
            String code = tradeRecord.getCode();
            XueqiuKline xueqiuKline = dateRecord.getValue();
            if (xueqiuKline==null) {
                buyCodeMap.put(code, String.join("|", DateUtil.formatDate(tradeRecord.getBuyTime()),
                        CalcUtil.strOf(tradeRecord.getBuyPrice()), CalcUtil.strOf(tradeRecord.getProfit())));
            }else {
                String divide = CalcUtil.divide(xueqiuKline.getMarket_capital(), 1E8).toString();
                buyCodeMap.put(code, String.join("|", DateUtil.formatDate(tradeRecord.getBuyTime()),
                        CalcUtil.strOf(tradeRecord.getBuyPrice()), CalcUtil.strOf(tradeRecord.getProfit()), divide));
            }
        }
        return buyCodeMap;
    }

    private List<Pair<TradeRecord, XueqiuKline>> sortList(Date date) {
        HoldRecord holdRecord = getHoldRecordMap().get(DateUtil.formatDate(date));
        if (holdRecord==null) {
            return new ArrayList<>();
        }
        List<Pair<TradeRecord, XueqiuKline>> dateRecords = holdRecord.getDateRecords();
        List<Pair<TradeRecord, XueqiuKline>> collect = dateRecords.stream()
                .filter(e->e.getValue()!=null&&e.getValue().getMarket_capital()!=null)
                .sorted(Comparator.comparing(e -> e.getValue().getMarket_capital(), Comparator.reverseOrder())).collect(Collectors.toList());
        List<Pair<TradeRecord, XueqiuKline>> collect1 = dateRecords.stream()
                .filter(e->e.getValue()==null).collect(Collectors.toList());
        collect.addAll(collect1);
        return collect;
    }

    private HoldRecord addHoldRecord(Date date, TradeRecord tradeRecord, XueqiuKline xueqiuKline) {
        HoldRecord value = getHoldRecordMap().getOrDefault(DateUtil.formatDate(date), new HoldRecord());
        value.setDate(date);
        List<Pair<TradeRecord, XueqiuKline>> dateRecords = value.getDateRecords();
        dateRecords.add(Pair.of(tradeRecord,xueqiuKline));
        getHoldRecordMap().putIfAbsent(DateUtil.formatDate(date), value);
        return value;
    }

    public void allSell(Date date, Map<String, XueqiuKline> klineMap) {
        for (List<TradeRecord> tradeRecords : getTradeRecordMap().values()) {
            for (TradeRecord tradeRecord : tradeRecords) {
                if (tradeRecord.isBought()) {
                    String code = tradeRecord.getCode();
                    if (klineMap.containsKey(code)) {
                        XueqiuKline xueqiuKline = klineMap.get(code);
                        Double close = xueqiuKline.getClose();
                        tradeRecord.recordSell(date, close, tradeRecord.getBuyVolume());
                        Double sellTotalPrice = tradeRecord.getSellTotalPrice();
                        statCashAsset(sellTotalPrice, true);
                    }
                }
            }
        }
        setStockAsset(0d);
        statAsset();
        Double profit = getAsset() - getInitCashAsset();
        Double avgCount = getHoldRecordMap().entrySet().stream().filter(e -> !e.getValue().getDateRecords().isEmpty())
                .collect(Collectors.averagingLong(e -> e.getValue().getDateRecords().size()));
        if (avgCount==0) {
            return;
        }
        log.info("{},asset:{},avgStockCount:{},avgStockProfit:{}", DateUtil.formatDate(date), CalcUtil.strOf(getAsset()), CalcUtil.strOf(avgCount), CalcUtil.doubleDivide(profit, avgCount));
    }


    private void statCashAsset(Double totalPrice, boolean isAdd) {
        if (isAdd) {
            setCashAsset(getCashAsset() + totalPrice);
        } else {
            setCashAsset(getCashAsset() - totalPrice);
        }
    }

    private Conclusion conclusion() {
        analyst.setInfoResources(getInfoResources());
        analyst.setTradeRecordMap(getTradeRecordMap());
        return analyst.conclusion();
    }

}